package com.xlabs.server.DO;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.annotations.Version;
import javax.jdo.annotations.VersionStrategy;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.User;
import com.xlabs.client.DTO.ReplyDTO;
import com.xlabs.client.DTO.TransferDTO;

/**
 * Reply Data object.
 */
@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Version(
		strategy=VersionStrategy.VERSION_NUMBER, 
		column = "version",
		extensions={@Extension(vendorName="datanucleus", key="field-name", value="version")})
public class Reply 
{

    /**
     * Primary key.
     */
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;
    
	/**
	 * JDO populated version.
	 */
	private Integer version;

    /**
     * The text comment.
     */
    @Persistent
    private String comment;

    /**
     * The video that has this comment.
     */
    @Persistent
    private Key video;

    /**
     * Replies to this comment.
     */
    @Persistent
    private List<Key> replies;

    /**
     * The parent reply/question for this reply.
     */
    @Persistent
    private Key replyTo;
    
    /**
     * The question of this reply.
     */
    @Persistent
    private Question question;

    /**
     * The parent comment. Is not null for all comments other than the root questions.
     */
    @Persistent
    private Key originalQuestion;
    
    /**
     * The question to which this reply has been selected to be the answer.
     */
    private Key answerFor;

	/**
     * When created.
     */
    @Persistent
    private Date createdOn;
    
    /**
     * Created on index.
     */
    @Persistent
    private String createdOnIndex;

    /**
     * Created by user.
     */
    @Persistent
    private User createdBy;

    /**
     * Initializes an instance of TimedComment.
     *
     * @param comment The comment.
     * @param createdBy User who created this reply. 
     */
    public Reply(String comment, Date createdOn, User createdBy)
    {
        this.comment = comment;
        this.createdOn = createdOn;
        this.createdBy = createdBy;
        this.createdOnIndex = createdOn.getTime() + "|" + createdBy.getEmail();
    }

	/**
	 * Necessary method for the RequestFactory.
	 * @param id The encoded key.
	 * @return The object with the given id.
	 */
	public static Reply findReply(String id)
	{
		if (id == null)
		{
			return null;
		}
		
    	PersistenceManager pm = PMF.get().getPersistenceManager();
    	try
    	{
    		Reply reply = pm.getObjectById(Reply.class, id);
    		return reply;
    	}
    	finally
    	{
    		pm.close();
    	}
	}
	
	/**
	 * Gets replies to a question according to the given index and range. 
	 * @param parentQuestionOrReplyKey Question key. 
	 * @param startIndexInc start Index.
	 * @param endIndexExcl end index. 
	 * @param direction query direction (left or right). 
	 * @param pageSize page size. 
	 * @param callback call back.
	 */
	@SuppressWarnings("unchecked")
	public static List<Reply> getRepliesByIndex(
			String parentQuestionOrReplyId, 
			String startIndexInc,
			String endIndexExcl, 
			int direction, 
			int pageSize)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
		try
		{
			Key parentQuestionOrReplyKey = KeyFactory.stringToKey(parentQuestionOrReplyId);
			
			String responsesQuery = "SELECT FROM "
				+ Reply.class.getName()
				+ " where createdOnIndex >= :startDateIndex && createdOnIndex < :endDateIndex && replyTo == :parentKey"
				+ " ORDER BY createdOnIndex "
				+ (direction == TransferDTO.RIGHT_DIRECTION ? " ASC " : " DESC ");
			Query query = pm.newQuery(responsesQuery);
			query.setRange(0, pageSize + 1);
		
			List<Reply> repliesList = (List<Reply>) query.execute(startIndexInc, endIndexExcl, parentQuestionOrReplyKey);
			repliesList.size();
			
    		if (direction == TransferDTO.LEFT_DIRECTION) 
    		{
    			repliesList = new ArrayList<Reply>(repliesList);
    			Collections.reverse(repliesList);
    		}

			return repliesList;
		}
		finally
		{
			pm.close();
		}
	}
	
	
	/**
     * Sets a reply as an answer or nonanswer to a question. 
     * @param questionId The question id
     * @param replyId The relpy id.
     * @param isAnswer whether to set the reply as an answer (or non-answer).
	 */
    public static void setReplyAsAnswer(
			String questionId, 
			String replyId,
			boolean isAnswer)
	{
    	Key questionKey = KeyFactory.stringToKey(questionId);
    	Key replyKey = KeyFactory.stringToKey(replyId);
    	
    	PersistenceManager pm = PMF.get().getPersistenceManager();
    	try
    	{
    		Question questionDO = pm.getObjectById(Question.class, questionKey);
    		Reply replyDO = pm.getObjectById(Reply.class, replyKey);

    		Reply previousReplyDO = null;
    		if (questionDO.getAnswer() != null)
    		{
    			previousReplyDO = pm.getObjectById(Reply.class, questionDO.getAnswer());
    		}
    		
    		if (isAnswer)
    		{
	    		questionDO.setAnswer(replyKey);
	    		replyDO.setAnswerFor(questionKey);
	    		if (previousReplyDO != null)
	    		{
	    			previousReplyDO.setAnswerFor(null);
	    		}
    		}
    		else
    		{
    			questionDO.setAnswer(null);
    			replyDO.setAnswerFor(null);
    		}
    	}
    	finally
    	{
    		pm.close();
    	}
	}
	
	/**
	 * Persist method used for RequesyFactory.
	 */
	public void persist() 
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			pm.makePersistent(this);
		}
		finally
		{
			pm.close();
		}
	}
	
	/**
	 * remove method used for RequesyFactory
	 */
	public void remove() 
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			pm.deletePersistent(this);
		}
		finally
		{
			pm.close();
		}
	}
	
    /**
     * Gets the key.
     *
     * @return id.
     */
    public Key getKey()
    {
        return key;
    }
    
	/**
	 * Gets the id of this question.
	 * @return Encoded key in string.
	 */
	public String getId()
	{
		return KeyFactory.keyToString(key);
	}

    /**
     * Sets the key.
     * @param key key.
     */
    public void setKey(Key key)
    {
        this.key = key;
    }
    
	/**
	 * Used by RequestFactory.
	 * @return
	 */
	public Integer getVersion()
	{
		return this.version;
	}
	
	/**
	 * Setter for {@link #version}
	 * @param version 
	 * @return
	 */
	public void setVersion(Integer version)
	{
		this.version = version;
	}

    /**
     * Gets comment.
     *
     * @return comment.
     */
    public String getReplyText() 
    {
        return comment;
    }
    
    /**
     * Sets Comment.
     *
     * @param comment The text comment.
     */
    public void setComment(String comment)
    {
        this.comment = comment;
    }

    /**
     * Gets video.
     *
     * @return video.
     */
    public Key getVideo() 
    {
        return video;
    }

    /**
     * Sets video
     *
     * @param video video that has this comment.
     */
    public void setVideo(Key video) 
    {
        this.video = video;
    }

    /**
     * Gets replies.
     * @return List of replies.
     */
    public List<Key> getReplies() 
    {
        return replies;
    }
    
    /**
     * Gets count of replies to this reply.
     * @return number of replies.
     */
    public Integer getReplyCount()
    {
    	return replies.size();
    }

    /**
     * Sets replies.
     * @param replies List of replies.
     */
    public void setReplies(List<Key> replies) 
    {
        this.replies = replies;
    }

    /**
     * Adds a reply.
     * @param replyKey reply key.
     */
    public void addReply(Key replyKey)
    {
        if (this.replies == null)
        {
            this.replies = new ArrayList<Key>();
        }
        this.replies.add(replyKey);
    }

    /**
     * Gets the parent comment.
     * @return parent comment.
     */
    public Key getOriginalQuestion() 
    {
        return originalQuestion;
    }

    /**
     * Sets relatedTo.
     * @param originalQuestion The original question to whose reply tree this reply blongs.
     */
    public void setOriginalQuestion(Key originalQuestion)
    {
        this.originalQuestion = originalQuestion;
    }

    /**
     * Returns the encoded Key of this jdo as a string.
     * @return encoded key.
     */
    public String getEncodedKey() 
    {
        return KeyFactory.keyToString(this.key);
    }

    /**
     * Gets replyTo
     * @return The key of the reply to which this is a reply to.
     */
    public Key getReplyTo()
    {
        return replyTo;
    }

    /**
     * Sets replyTo.
     * @param replyTo Question to which this is a reply.
     */
    public void setReplyTo(Key replyTo)
    {
        this.replyTo = replyTo;
    }

    /**
     * Gets createdOn.
     * @return time of creation.
     */
    public Date getCreatedOn() {
        return createdOn;
    }
    
	/**
	 * Gets {@link #createdOnIndex}
	 * @return {@link #createdOnIndex}
	 */
    public String getCreatedOnIndex()
	{
		return this.createdOnIndex;
	}

    /**
     * Sets time of creation.
     * @param createdOn time of creation.
     */
    public void setCreatedOn(Date createdOn) 
    {
        this.createdOn = createdOn;
    }

    /**
     * Gets createdBy.
     * @return creator.
     */
    public User getCreatedBy()
    {
        return createdBy;
    }
    
	/**
	 * Gets the name of the creator.
	 * @return
	 */
	public String getCreatedByName()
	{
		return createdBy.getNickname();
	}

    /**
     * Sets createdBy.
     * @param createdBy creator.
     */
    public void setCreatedBy(User createdBy)
    {
        this.createdBy = createdBy;
    }
    
    /**
     * Converts an array of CommentDO objects to an array of TimedCommentDTO.
     * @param allReplyDOs
     * @return
     */
    public static ReplyDTO[] toDTOArray(List<Reply> allReplyDOs) 
    {
        ReplyDTO[] replies = new ReplyDTO[allReplyDOs.size()];

        int index = 0;
        for (Reply aReplyDO : allReplyDOs) {
            replies[index++] = aReplyDO.toReplyDTO();
        }

        return replies;
    }
    
    /**
     * Converts to list of DTO objects.
     * @param replyDOs List of DOs.
     * @return List of DTO objects.
     */
	public static List<ReplyDTO> toDTOList(List<Reply> replyDOs)
    {
    	List<ReplyDTO> dtoList = new ArrayList<ReplyDTO>(replyDOs.size());
    	
    	for (Reply aDo : replyDOs)
    	{
    		dtoList.add(aDo.toReplyDTO());
    	}
    	
    	return dtoList;
    }

    /**
     * Converts this object to CommentDTO.
     * @return com.xlabs.client.CommentDTO
     */
    public ReplyDTO toReplyDTO() 
    {
        ReplyDTO dto = new ReplyDTO();

        dto.setReplyText(getReplyText());
        dto.setEncodedKey(getEncodedKey());
        dto.setCreatedBy(getCreatedBy().getNickname());
        dto.setCreatedOn(getCreatedOn());
        dto.setCreatedOnIndex(getCreatedOnIndex());
        dto.setReplyCount(getReplies().size());
        dto.setAnswer(answerFor != null);

        return dto;
    }

	/**
	 * Gets a question responses according to the "createdon" index and range. 
	 * @param questionEncodedKey Question key. 
	 * @param startIndexInc start Index.
	 * @param endIndexExcl end index. 
	 * @param direction query direction (left or right). 
	 * @param pageSize page size. 
	 * @param callback call back.
	 */
    @SuppressWarnings("unchecked")
	public static QueryResult<List<Reply>> queryResponsesByCreatedOnIndex(
			Key parentQuestionOrReplyKey,
			String startIndexInc, 
			String endIndexExcl, 
			int direction,
			int pageSize, 
			PersistenceManager pm)
	{
		String responsesQuery = "SELECT FROM "
			+ Reply.class.getName()
			+ " where createdOnIndex >= :startDateIndex && createdOnIndex < :endDateIndex && replyTo == :parentKey"
			+ " ORDER BY createdOnIndex "
			+ (direction == TransferDTO.RIGHT_DIRECTION ? " ASC " : " DESC ");

		Query query = pm.newQuery(responsesQuery);
		query.setRange(0, pageSize);
	
		List<Reply> responsesList = (List<Reply>) query.execute(startIndexInc, endIndexExcl, parentQuestionOrReplyKey);
		
		return new QueryResult<List<Reply>>(responsesList);
	}

	/**
	 * Setter for {@link #answerFor}
	 * @param answerFor the answerFor to set
	 */
	public void setAnswerFor(Key answerFor)
	{
		this.answerFor = answerFor;
	}
	
	/**
	 * Setter for {@link #answerFor} by string key.
	 * @param answerForId
	 */
	public void setAnswerForId(String answerForId)
	{
		Key answerForKey = null;
		if (answerForId != null)
		{
			answerForKey = KeyFactory.stringToKey(answerForId);
		}
		this.answerFor = answerForKey;
	}
	
	/**
	 * Returns answer for id.
	 * @return
	 */
	public String getAnswerForId()
	{
		return this.answerFor == null ? null : KeyFactory.keyToString(this.answerFor);
	}
	
	/**
	 * Getter for {@link #answerFor}
	 * @return the answerFor
	 */
	public Key getAnswerFor()
	{
		return answerFor;
	}
	
	/**
	 * Whether this reply is an answer.
	 * @return true iff reply is an answer.
	 */
	public Boolean getIsAnswer()
	{
		return answerFor != null;
	}

	/**
	 * Setter for {@link #question}
	 * @param question the question to set
	 */
	public void setQuestion(Question question)
	{
		this.question = question;
	}

	/**
	 * Getter for {@link #question}
	 * @return the question
	 */
	public Question getQuestion()
	{
		return question;
	}
}